# Python 的静态类型注解功能是 Python 3.5 引入的一个重要特性，通过 typing 模块和相关的语法支持，
# 为开发者提供了在代码中添加类型提示的能力。尽管 Python 是一种动态类型语言（变量的类型在运行时决定），
# 但静态类型注解可以让代码更具可读性、可维护性，并且能够利用静态类型检查工具（如 mypy）在开发阶段捕获潜在的类型错误。
from dataclasses import Field

from pydantic import BaseModel

#################################################### 静态类型修饰基本类型变量

# 语法：
# 变量 : 类型 = 值

a: int = 1
b: float = 1.1
c: str = '123'
d: bool = True

#################################################### typing 模块对基本类型的支持
from typing import List, Dict, Tuple, Optional, Union, AnyStr

# List[int]: 表示numbers是一个int类型集合
numbers: List[int] = [1, 2, 3]

# List[str]: 表示abc是一个字符串类型集合
abc: List[str] = ['a', 'b', 'c']

# Dict[str,float]: 表示my_dict是一个字典，key为字符串，value为float
my_dict: Dict[str, float] = {'alice': 1.1}

# Tuple[bool,bool]: 元组,
point: Tuple[bool, bool] = (True, False)

# Optional[int]:可选类型,Optional[int] 表示该值可以是 int 或 None。
# None不用单独写在[]中
maybe_number: Optional[int] = None

# Union[int,str]: 表示该值可以是 int 或 str。
maybe_int_or_str: Union[int, str] = 1


#################################################### 静态类型在方法上的运用

# :int :表示入参a和b都是int类型，
# -> int : 表示返回值也是int类型
def add_number(a: int, b: int) -> int:
    return a + b


# -> float: 表示返回值是float浮点型
def toFloat(a) -> float:
    return float(a)


# -> float: 表示返回值是bool类型
def toBool(a) -> bool:
    return bool(a)


# -> str: 表示返回值是字符窜类型
def toString(a) -> str:
    return str(a)


# -> dict: 表示返回类型是字典类型
def toDict(a) -> dict:
    return dict(a)


# -> dict: 表示返回类型list
def toList(a) -> list:
    return list(a)


# -> dict: 表示返回类型tuple
def toTuple(a) -> tuple:
    return tuple(a)


# -> dict: 表示返回类型是set
def toSet(a) -> set:
    return set(a)


print('*' * 40)

####################################################  TypedDict
# TypedDict 是 Python 的 typing 模块中的一部分，自 Python 3.8 开始引入。它允许你定义具有特定结构的字典类型，即字典中的键和每个键对应的值类型都是已知且固定的。这为静态类型检查提供了便利，使得代码不仅更易读，而且在编译期（或使用静态类型检查工具如 mypy）就能发现潜在的类型错误。
#
# 主要用途
# 1.定义固定结构的字典
# 在某些情况下，你需要确保一个字典包含特定的键，并且这些键对应的值是特定类型的。例如，配置文件、API 响应等场景下，可以使用 TypedDict 来明确这些要求。
# 2.提高代码可读性和可维护性
# 明确定义字典的结构有助于其他开发者理解你的意图，同时也方便日后维护代码时快速了解数据结构的要求。
# 3.静态类型检查
# 使用 TypedDict 可以让静态类型检查器（比如 mypy）帮助你在开发过程中捕获类型相关的错误，而不需要等到运行时才发现问题。

from typing import TypedDict


# 定义类的时候写死TypedDict
class Movie(TypedDict):
    # 定义name为str
    name: str
    # 定义year为int
    year: int
    # 定义director为str或者None
    director: Optional[str] = None


movie1: Movie = {
    "name": '东成西就',
    "year": 1994,
}

print(movie1)

movie2: Movie = {
    "name": 123,
    "year": '1994'
}
print(movie2)

movie3: Movie = {
    "name": 123,
    "year": '1994',
    "role": "周星驰"
}
print(movie3)

# 运行命令：mypy module_demo_typing.py
# 输出：
# module_demo_typing.py:4: error: Library stubs not installed for "lxml.etree"  [import-untyped]
# module_demo_typing.py:4: note: Hint: "python3 -m pip install lxml-stubs"
# module_demo_typing.py:4: note: (or run "mypy --install-types" to install all missing stub packages)
# module_demo_typing.py:4: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
# module_demo_typing.py:94: error: Right hand side values are not supported in TypedDict  [misc]
# PS D:\workspace\github\pythondemo> cd chap10
# PS D:\workspace\github\pythondemo\chap10> mypy module_demo_typing.py
# module_demo_typing.py:4: error: Library stubs not installed for "lxml.etree"  [import-untyped]
# module_demo_typing.py:4: note: Hint: "python3 -m pip install lxml-stubs"
# module_demo_typing.py:4: note: (or run "mypy --install-types" to install all missing stub packages)
# module_demo_typing.py:4: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
# module_demo_typing.py:94: error: Right hand side values are not supported in TypedDict  [misc]
#
# 以下是movie类型错误
#
# module_demo_typing.py:97: error: Missing key "director" for TypedDict "Movie"  [typeddict-item]
# module_demo_typing.py:104: error: Missing key "director" for TypedDict "Movie"  [typeddict-item]
# module_demo_typing.py:110: error: Missing key "director" for TypedDict "Movie"  [typeddict-item]
# module_demo_typing.py:110: error: Extra key "role" for TypedDict "Movie"  [typeddict-unknown-key]
# module_demo_typing.py:111: error: Incompatible types (expression has type "int", TypedDict item "name" has type "str")  [typeddict-item]
# module_demo_typing.py:112: error: Incompatible types (expression has type "str", TypedDict item "year" has type "int")  [typeddict-item]

print('-' * 40)

#################################################### Any
# Any 被定义在typing模块中，表示任意类型

# 导入Any
from typing import Any


def printClass(obj: Any):
    print(f"传进来的类型是：{type(obj)}, 值是{obj}")


printClass('1')
printClass(1)
printClass(True)
printClass(dict({'a': 1, 'b': 2, 'c': 3}))

print('-' * 40)


####################################################  类型别名

# 类型别名是一个新的名字，用来表示某种类型或一组类型的组合。它并不创建新的类型，而是为现有类型提供一个更简洁、更具描述性的名称。
# 类型别名语法如下：
# NewTypeName = ExistingType
#
# NewTypeName 是你为类型定义的别名。
# ExistingType 是你想要简化或重命名的复杂类型。

# 不使用类型别名之前的写法
def process_numbers_one(data: Dict[str, List[int]]):
    for key, value in data.items():
        print(f"{key}:{sum(value)}")


process_numbers_one({'k1': [1, 2], 'k2': [3, 4]})

# 使用类型别名的写法
# 设置别名
DictNumbersList = Union[Dict[str, List[int]]]


def process_numbers_two(data: DictNumbersList):
    for key, value in data.items():
        print(f"{key}:{sum(value)}")


process_numbers_two({'k1': [10, 20], 'k2': [30, 40]})

#
# 总结：如何快速区分类型别名和变量？
# 特性	            类型别名	                        变量
# 右侧内容	       类型（如 int、List[int] 等）	  数据值（如 1、[1, 2, 3] 等）
# 用途	           类型注解	                      存储数据
# 是否需要typing	   通常需要	                      不需要
# 静态类型检查	   识别为类型	                      被识别为变量
# 示例	           Numbers = List[int]	          numbers = [1, 2, 3]

print('-' * 40)

####################################################  Annotated
# Annotated允许我们在类型注解中附加额外的元信息（metadata），而不会影响类型的语义。
# 定义一个带有元信息的类型
# MyType = Annotated[BaseType, Metadata1, Metadata2, ...]
# BaseType：基础类型，比如 int, str, List[int] 等。
# Metadata1, Metadata2, ...：附加的元信息，可以是任何对象（如字符串、类、函数等）。
# Annotated 的主要作用是为类型注解提供一种标准化的方式，嵌入更多的上下文信息。

# Annotated 的核心思想是将基础类型和元信息分开存储，这样框架或工具可以在运行时提取这些信息并加以利用。

from typing import Annotated, get_args, get_origin
from pydantic import BaseModel, Field

# 定义一个带有原信息的类型
# 基础类型：int
# 元数据：positive 和 lambda x: x > 0
MyType = Annotated[int, "positive", lambda x: x > 0]

# 输出：<class 'typing._AnnotatedAlias'>
print(type(MyType))

# 提取基础类型: typing.Annotated
# 这是因为get_origin中代码写的就是如果类型是<class 'typing._AnnotatedAlias'>就返回typing.Annotated
print(get_origin(MyType))

# 提取原信息: 输出元祖如下:(<class 'int'>, 'positive', <function <lambda> at 0x00000247A5D76FC0>)
print(get_args(MyType))
tuples = get_args(MyType)
# 输出：<class 'int'>
print(tuples[0])
# 输出：positive
print(tuples[1])
# 输出：<function <lambda> at 0x00000252F1B96FC0>
print(tuples[2])


# Annotated用法一：数据校验

class MyModel(BaseModel):
    # 使用pydantic的Field作为number的元数据,校验值是否小于0
    number: Annotated[int, Field(ge=0)]


try:
    model = MyModel(number=-1)
except ValueError as e:
    print(e)
